import datetime
from typing import Optional
import qstock as qs
from app import pro
import pandas as pd
import numpy as np
from app.core.MyTT import MA,MACD,CROSS,judge_price_cross_ma60,judge_price_cross_ma13,judge_price_cross_ma5,SLOPE
from typing import Dict, Any, List

class StockUtils:
    """
    股票工具类
    """

    #完善股票代码
    @staticmethod
    def fix_tushare_stock_code(stock_code: str) -> str:
        """
        完善股票代码，添加交易所后缀
        :param stock_code: 股票代码
        :return: 完善后的股票代码
        """
        if stock_code.startswith('6'):
            return f"{stock_code}.SH"
        elif stock_code.startswith(('0', '3')):
            return f"{stock_code}.SZ"
        else:
            return stock_code

    @staticmethod
    def get_latest_quarter_dates() -> str:
        """
        获取上一个季度财报公告的日期，返回季度最后一天，如20250630
        :return: 日期，格式为YYYYMMDD
        """
        today = datetime.date.today()
        
        # 计算上一个季度
        current_quarter = (today.month - 1) // 3 + 1
        previous_quarter = current_quarter - 1
        
        if previous_quarter == 0:
            # 上一年的第四季度
            last_day = datetime.date(today.year - 1, 12, 31)
        elif previous_quarter == 1:
            last_day = datetime.date(today.year, 3, 31)
        elif previous_quarter == 2:
            last_day = datetime.date(today.year, 6, 30)
        else:  # previous_quarter == 3
            last_day = datetime.date(today.year, 9, 30)
        
        # 格式化为YYYYMMDD字符串
        return last_day.strftime('%Y%m%d')
    
    @staticmethod
    def get_stock_code(stock_name: Optional[str|list]) -> Optional[str|list]:
        """
        获取股票代码
        :param stock_name: 股票名称
        :return: 股票代码
        """
        if isinstance(stock_name, str):
            return stock_name[:6]
        elif isinstance(stock_name, list):
            df = qs.stock_basics(stock_name)
            #取df第一列和第二列的值
            return df.iloc[:, [0, 1]].values.tolist()
        else:
            return None
    
    @staticmethod
    def get_stock_name(stock_code: str) -> str:
        """
        获取股票名称
        :param stock_code: 股票代码
        :return: 股票名称
        """
        # 获取基本信息
        if stock_code.startswith('6'):
            ts_code = f"{stock_code}.SH"
        elif stock_code.startswith(('0', '3')):
            ts_code = f"{stock_code}.SZ"
        else:
            print(f"不支持的股票代码: {stock_code}")
            return None
            
        # 获取股票名称
        stock_name = pro.stock_basic(ts_code=ts_code, fields='name').iloc[0]['name']
        return stock_name
    
    @staticmethod
    def get_bg_rate(stock_code: str) -> str:
        """
        获取股票的营收和净利润增速
        :param stock_code: 股票代码
        :return: 营收和净利润增速，格式为tr_yoy-dt_netprofit_yoy
        """
        #完善股票代码
        stock_code = StockUtils.fix_tushare_stock_code(stock_code)
        #获取上个季度最后一天
        last_quarter_date = StockUtils.get_latest_quarter_dates()
        #获取营收和净利润增速
        increase_df = pro.fina_indicator(ts_code=stock_code, period=last_quarter_date, fields='ts_code,tr_yoy,netprofit_yoy,dt_netprofit_yoy')
        #获取increase_df值的tr_yoy和dt_netprofit_yoy，组成一个字符串，格式为tr_yoy-dt_netprofit_yoy
        if not increase_df.empty:
            tr_yoy = increase_df.iloc[0]['tr_yoy']
            netprofit_yoy = increase_df.iloc[0]['netprofit_yoy']
            dt_netprofit_yoy = increase_df.iloc[0]['dt_netprofit_yoy']
        else:
            tr_yoy = 0
            netprofit_yoy = 0
            dt_netprofit_yoy = 0
        #将tr_yoy和dt_netprofit_yoy格式化为字符串,营收-净利润-归母净利润
        bg_rate = f"{tr_yoy:.2f}-{netprofit_yoy:.2f}-{dt_netprofit_yoy:.2f}"
        return bg_rate
    
    @staticmethod
    def get_stock_data(stock_code: str, start_date: str = '20240601',exit_df: Optional[pd.DataFrame] = None) -> Optional[pd.DataFrame]:
        """
        获取股票数据
        :param stock_code: 股票代码
        :param start_date: 开始日期，格式为YYYYMMDD
        :return: 股票数据，格式为date,open,high,low,close,volume,amount
        """
        df = qs.get_data(stock_code, start=start_date)
        if df is not None and not df.empty:
            # 4. 将name这列去掉
            if 'name' in df.columns:
                df = df.drop(columns=['name'])
            #将df中索引date当成date列并放在第一列
            df['date'] = df.index.strftime('%Y%m%d')
            #将date列放在第一位
            cols = list(df.columns)
            cols.remove('date')
            cols.insert(0, 'date')
            df = df[cols]
            #将df中code和volume列改名为ts_code和vol
            df.rename(columns={'code': 'ts_code', 'volume': 'vol'}, inplace=True)
            #移除df索引
            df.reset_index(drop=True, inplace=True)
            #将df按照date列asc排序
            df.sort_values(by='date', ascending=True, inplace=True)
            #如果exit_df不是空的，将df与exit_df合并
            if exit_df is not None:
                # 合并df和exit_df
                df = pd.concat([exit_df, df], ignore_index=True)
                #按日期排序
                df = df.sort_values('date').reset_index(drop=True)
            print(df.tail(5))
            #计算股价每日涨跌幅
            df['pct_change'] = df['close'].pct_change()
            # 将小数形式的涨跌幅改为百分比形式 (+5% 或 -5%)
            df['pct_change'] = df['pct_change'].apply(lambda x: f"{x*100:+.2f}%" if pd.notna(x) else x)
            #计算ma5/13/60日均线，并保留2位小数
            df['ma5'] = MA(df['close'], 5).round(2)
            df['ma13'] = MA(df['close'], 13).round(2)
            df['ma60'] = MA(df['close'], 60).round(2)
            #计算MACD，并保留2位小数
            dif, dea, macd = MACD(df['close'], 12, 26, 9)
            df['dif'] = dif.round(2)
            df['dea'] = dea.round(2)
            df['macd'] = macd.round(2)

            
        return df
    

    @staticmethod
    def get_dlzz(df: pd.DataFrame) -> pd.DataFrame:
        """
        获取符合登陆作战数据
        :param df: 股票数据
        :return: 股票数据df
        df格式为：(date VARCHAR, ts_code VARCHAR, open FLOAT, high FLOAT, low FLOAT, "close" FLOAT, vol FLOAT, turnover FLOAT, turnover_rate FLOAT, pct_change VARCHAR, ma5 FLOAT, ma13 FLOAT, ma60 FLOAT, dif FLOAT, dea FLOAT, macd FLOAT);
        """
        #在df中取出最后一天的数据，date格式为YYYYMMDD，只要时间，不要股票代码
        df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')
        latest_date = df['date'].max().strftime('%Y%m%d')
        #取出df中date为latest_date的行
        last_day_df = df[df['date'] == latest_date]
        
        # 将结果添加到dataframe中
        last_day_df['price_cross_ma60'] = judge_price_cross_ma60(last_day_df)
        # 查看满足条件的日期
        cross_ma60 = last_day_df[last_day_df['price_cross_ma60']]        

        return cross_ma60
    
    @staticmethod
    def get_jtcl(df: pd.DataFrame) -> pd.DataFrame:
        """
        获取符合卷土重来战法的股票
        :param df: 股票数据
        :return: 股票数据df
        df格式为：(date VARCHAR, ts_code VARCHAR, open FLOAT, high FLOAT, low FLOAT, "close" FLOAT, vol FLOAT, turnover FLOAT, turnover_rate FLOAT, pct_change VARCHAR, ma5 FLOAT, ma13 FLOAT, ma60 FLOAT, dif FLOAT, dea FLOAT, macd FLOAT);
        """
        #计算13日均线斜率
        df['ma13_slope'] = SLOPE(df['ma13'], 13)
        #在df中取出最后一天的数据，date格式为YYYYMMDD，只要时间，不要股票代码
        df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')
        latest_date = df['date'].max().strftime('%Y%m%d')
        #取出df中date为latest_date的行
        last_day_df = df[df['date'] == latest_date]

        last_day_df['price_cross_ma13'] = judge_price_cross_ma13(last_day_df)

        # 查看满足上穿13日线并且13日均线斜率>0的df
        cross_ma13 = last_day_df[(last_day_df['price_cross_ma13']) & (last_day_df['ma13_slope'] > 0)]

        return cross_ma13        
        
    @staticmethod
    def get_kzjy(df: pd.DataFrame) -> pd.DataFrame:
        """
        获取符合空中加油战法的股票
        :param df: 股票数据
        :return: 股票数据df
        df格式为：(date VARCHAR, ts_code VARCHAR, open FLOAT, high FLOAT, low FLOAT, "close" FLOAT, vol FLOAT, turnover FLOAT, turnover_rate FLOAT, pct_change VARCHAR, ma5 FLOAT, ma13 FLOAT, ma60 FLOAT, dif FLOAT, dea FLOAT, macd FLOAT);
        """
        # 计算5日均线斜率
        df['ma5_slope'] = SLOPE(df['ma5'], 5)
        #计算13日均线斜率
        df['ma13_slope'] = SLOPE(df['ma13'], 13)
        #在df中取出最后一天的数据，date格式为YYYYMMDD，只要时间，不要股票代码
        df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')
        latest_date = df['date'].max().strftime('%Y%m%d')
        #取出df中date为latest_date的行
        last_day_df = df[df['date'] == latest_date]

        last_day_df['price_cross_ma5'] = judge_price_cross_ma5(last_day_df)
       

        # 查看满足上穿5日线并且5日均线斜率和13日均线斜率>0的df
        cross_ma5 = last_day_df[(last_day_df['price_cross_ma5']) & (last_day_df['ma5_slope'] > 0) & (last_day_df['ma13_slope'] > 0)]

        return cross_ma5
    
    #阳包阴战法
    @staticmethod
    def get_bullish_engulfing_pattern(df: pd.DataFrame) -> List[Dict[str, Any]]:
        """
        获取符合阳包阴战法的股票
        :param df: 股票数据
        :return: 股票数据列表
        df格式为：(date VARCHAR, ts_code VARCHAR, open FLOAT, high FLOAT, low FLOAT, "close" FLOAT, vol FLOAT, turnover FLOAT, turnover_rate FLOAT, pct_change VARCHAR, ma5 FLOAT, ma13 FLOAT, ma60 FLOAT, dif FLOAT, dea FLOAT, macd FLOAT);
        """
        #在df中取出最后一天的数据，date格式为YYYYMMDD，只要时间，不要股票代码
        df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')
        latest_date = df['date'].max().strftime('%Y%m%d')
        # 处理pct_change列（通常是字符串格式），值为+3.97%，需要去掉+、-和%，并将数字转换为数值
        df['pct_change_num'] = df['pct_change'].str.replace('+','').str.replace('-','').str.replace('%','').astype(float)
        #判断是否为阴线或者十字星
        df['is_negative_positive'] = df['open'] >= df['close']
        #pct_change字符串包含+则是阳线，并且pct_change_num大于3.0
        df['is_large_positive'] = df['pct_change'].str.contains(r'\+') & (df['pct_change_num'] >= 3.0)
        #对每根K线做个标记，如果是大阳线，则标记true，下次不会再使用
        df['marked'] = False
        '''
        识别“看涨吞没形态”（阳包阴）。
        标准：
        - 第一根蜡烛：大幅上涨（涨跌幅 > 3%）。
        - 第二根蜡烛：下跌（收盘价 <= 开盘价）。
        - 第三根蜡烛：大幅上涨（涨跌幅 > 3%）。
        - 第三根蜡烛的开盘价 < 第一根蜡烛的收盘价。
        - 第三根蜡烛的收盘价 > 第一根蜡烛的收盘价。
        参数：
            df_with_helpers（pandas.DataFrame）：带有辅助列的DataFrame。
        返回：
            元组：已识别记录的列表和带有标记蜡烛的更新后的DataFrame。
        '''
        results = pd.DataFrame()
        #df中有每只股票近2年的K线数据，需要根据ts_code依次遍历
        for ts_code in df['ts_code'].unique():
            #取出df中ts_code为ts_code的行
            df_ts_code = df[df['ts_code'] == ts_code]
            # 从往前100天开始循环
            start_index = max(0, len(df_ts_code) - 100)
            for i in range(start_index, len(df_ts_code) -2):
                #检查三根K线中是否有任何一根已被标记
                if df_ts_code.iloc[i]['marked'] or df_ts_code.iloc[i+1]['marked'] or df_ts_code.iloc[i+2]['marked']:
                    continue
                first_candle = df_ts_code.iloc[i]
                middle_candle = df_ts_code.iloc[i+1]
                third_candle = df_ts_code.iloc[i+2]

                if (first_candle['is_large_positive'] and 
                    middle_candle['is_negative_positive'] and # 阴线
                    third_candle['is_large_positive'] and
                    third_candle['open'] < first_candle['close'] and
                    third_candle['close'] > first_candle['close']):

                    # # Add first candle record
                    # first_record = first_candle.copy()
                    # first_record['pattern'] = '阳包阴'
                    # first_record['recognition_date'] = third_candle['date']
                    # results.append(first_record)

                    # # Add third candle record
                    # third_record = third_candle.copy()
                    # third_record['pattern'] = '阳包阴'
                    # third_record['recognition_date'] = third_candle['date']
                    # results.append(third_record)
                    
                    #如果second_candle['date']=latest_date,则将second_candle放到results中
                    if third_candle['date'] == latest_date:
                        results = pd.concat([results, third_candle.to_frame().T], ignore_index=True)

                    # Mark the three candles as used
                    df_ts_code.iloc[i, df_ts_code.columns.get_loc('marked')] = True
                    df_ts_code.iloc[i+1, df_ts_code.columns.get_loc('marked')] = True
                    df_ts_code.iloc[i+2, df_ts_code.columns.get_loc('marked')] = True
        
        return results

    @staticmethod
    def get_identify_general_rising_pillar_pattern(df: pd.DataFrame, max_gap: int = 15) -> List[Dict[str, Any]]:
        """
        获取符合将军柱战法的股票
        :param df: 股票数据
        :return: 股票数据列表
        df格式为：(date VARCHAR, ts_code VARCHAR, open FLOAT, high FLOAT, low FLOAT, "close" FLOAT, vol FLOAT, turnover FLOAT, turnover_rate FLOAT, pct_change VARCHAR, ma5 FLOAT, ma13 FLOAT, ma60 FLOAT, dif FLOAT, dea FLOAT, macd FLOAT);
        """
        '''
        识别“将军柱形态”（将军柱）。
        标准：
        - 第一根蜡烛：大幅上涨（涨跌幅 > 3%）。
        - 第二根蜡烛：大幅上涨（涨跌幅 > 3%）。
        - 第二根蜡烛的开盘价 < 第一根蜡烛的收盘价。
        - 第二根蜡烛的收盘价 > 第一根蜡烛的收盘价。
        - 这两根蜡烛之间最多可以有“max_gap”根蜡烛（默认值为15）。
        - 如果超过间隔，则放弃第一根蜡烛，第二根蜡烛成为新的起点。
        参数：
            df（pandas.DataFrame）：包含辅助列的数据框。
            max_gap（int）：两根柱子之间允许的最大蜡烛数量。
        返回：
            元组：已识别记录的列表和带有标记蜡烛的更新后的数据框。
        '''
        #在df中取出最后一天的数据，date格式为YYYYMMDD，只要时间，不要股票代码
        df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')
        latest_date = df['date'].max().strftime('%Y%m%d')
        # 处理pct_change列（通常是字符串格式），值为+3.97%，需要去掉+、-和%，并将数字转换为数值
        df['pct_change_num'] = df['pct_change'].str.replace('+','').str.replace('-','').str.replace('%','').astype(float)
        #判断是否为阴线或者十字星
        df['is_negative_positive'] = df['open'] >= df['close']
        #pct_change字符串包含+则是阳线，并且pct_change_num大于3.0
        df['is_large_positive'] = df['pct_change'].str.contains(r'\+') & (df['pct_change_num'] >= 3.0)
        #对每根K线做个标记，如果是大阳线，则标记true，下次不会再使用
        df['marked'] = False
        results = pd.DataFrame()
        
        for ts_code in df['ts_code'].unique():
            #取出df中ts_code为ts_code的行
            df_ts_code = df[df['ts_code'] == ts_code]
            #取df前100天4个月的K线数据，之前的没有意义了
            i = len(df_ts_code) - 100
            while i < len(df_ts_code):
                if df_ts_code.iloc[i]['marked'] or not df_ts_code.iloc[i]['is_large_positive']:
                    i += 1
                    continue

                first_candle = df_ts_code.iloc[i]  # 获取当前K线作为候选的第一根阳线
                first_candle_index = i
                found_second = False  # 初始化标志，表示尚未找到匹配的第二根阳线
                j = i + 3   # 初始化内层循环的索引 j，从第三根阳线开始，避免出现阳包阴
                while j < len(df_ts_code):
                    if df_ts_code.iloc[j]['marked']:
                        j += 1
                        continue

                    second_candle = df_ts_code.iloc[j]

                    # 检查间隔 (gap_count) 是否超过 max_gap
                    gap_count = j - first_candle_index - 1 #计算两根K线之间的间隔（不包括第一根K线）
                    if gap_count > max_gap:
                        print(f"    Gap ({gap_count}) between {first_candle['date']} and {second_candle['date']} exceeds max ({max_gap}). Abandoning {first_candle['date']}.")
                        #如果是，说明第一根阳线 first_candle 和当前的候选第二根阳线 second_candle 之间隔得太远了，不符合“将军柱”的新规则。需要放弃第一根阳线。
                        i = j - 16
                        found_second = False # Indicate that no pattern was found with the original first candle
                        break # Break the inner loop to restart search from j

                    # Check pattern criteria
                    if (second_candle['is_large_positive'] and
                        second_candle['open'] < first_candle['close'] and
                        second_candle['close'] > first_candle['close']):
                        
                        # # Add first candle record
                        # first_record = first_candle.copy()
                        # first_record['pattern'] = '将军柱'
                        # first_record['recognition_date'] = second_candle['date']
                        # results.append(first_record)

                        # # Add second candle record
                        # second_record = second_candle.copy()
                        # second_record['pattern'] = '将军柱'
                        # second_record['recognition_date'] = second_candle['date']
                        # results.append(second_record)
                        #如果second_candle['date']=latest_date,则将second_candle放到results中
                        if second_candle['date'] == latest_date:
                            results = pd.concat([results, second_candle.to_frame().T], ignore_index=True)

                        # Mark the two candles as used
                        df_ts_code.iloc[first_candle_index, df_ts_code.columns.get_loc('marked')] = True
                        df_ts_code.iloc[j, df_ts_code.columns.get_loc('marked')] = True
                        print(f"  - Found General Rising Pillar: {first_candle['date']} and {second_candle['date']}")
                        
                        found_second = True
                        # Move index to j+1 to continue search after the second candle
                        i = j + 1
                        # Break the inner loop as we found a match for the current first candle
                        break 
                    
                    j += 1
                
                # If the inner loop completed without finding a match or exceeding the gap,
                # and we didn't break due to gap, then no match was found for the current first candle.
                # Move to the next unmarked candle.
                if not found_second and j >= len(df_ts_code):
                    i += 1

        return results

    @staticmethod
    def get_anndate_previous_last_data(ts_code:str,ann_date:str,type:str) -> bool:
        """
        获取ann_date的前一天或者后一天的股票日数据，ann_date是字符串YYYYMMDD格式
        ts_code是股票代码
        type是股票类型，1是前一天，2是后一天
        """
        # 获取基本信息
        if ts_code.startswith('6'):
            ts_code = f"{ts_code}.SH"
        elif ts_code.startswith(('0', '3')):
            ts_code = f"{ts_code}.SZ"
        #取ann_date的前一天，ann_date是字符串YYYYMMDD格式
        df = pro.daily(ts_code=ts_code, start_date='20250922')
        if type == '1':
            #df中的trade_date的值与ann_date相等的行
            df_ann_date = df[df['trade_date'] == ann_date]
            # 检查是否找到了匹配的行
            if df_ann_date.empty:
                return False  # 或者抛出一个更明确的异常
            #取df_ann_date的前一行，这里应该是df['trade_date'] == ann_date的前一行
            # 获取df中ann_date行的索引
            ann_date_indices = df[df['trade_date'] == ann_date].index
            if len(ann_date_indices) == 0:
                return False  # 或者抛出一个更明确的异常
            ann_date_index = ann_date_indices[0]
            #检查索引是否有效（不是最后一行）
            if ann_date_index + 1 >= len(df):
                return False  # 没有前一行数据
            #取df_ann_date的前一行，这里应该是df['trade_date'] == ann_date的前一行
            df_previous = df.iloc[ann_date_index + 1]
            #用df_ann_date和df_previous判断是否为跳空高开
            if df_ann_date.iloc[0]['open'] > df_previous['close']:
                return True
            else:
                return False
        elif type == '2':
            #df中的trade_date的值与ann_date相等的行
            df_ann_date = df[df['trade_date'] == ann_date]
            # 检查是否找到了匹配的行
            if df_ann_date.empty:
                return False  # 或者抛出一个更明确的异常
            #取df_ann_date的后一行
            # 获取df中ann_date行的索引
            ann_date_indices = df[df['trade_date'] == ann_date].index
            if len(ann_date_indices) == 0:
                return False  # 或者抛出一个更明确的异常
            ann_date_index = ann_date_indices[0]
            #检查索引是否有效（不是第一行）
            if ann_date_index - 1 < 0:
                return False  # 没有后一行数据
            #取df_ann_date的后一行，这里应该是df['trade_date'] == ann_date的后一行
            df_next = df.iloc[ann_date_index - 1]
            #用df_ann_date和df_next判断是否为跳空高开
            if df_next['open'] > df_ann_date.iloc[0]['close']:
                return True
            else:
                return False

stockUtils:StockUtils = StockUtils()